package app

import (
	"context"
	"net/http"
	"path/filepath"
	"strconv"
	"strings"
	"time"

	"github.com/glennliao/apijson-go"
	"github.com/glennliao/apijson-go/model"
	"github.com/gogf/gf/v2/container/gvar"
	"github.com/gogf/gf/v2/errors/gerror"
	"github.com/gogf/gf/v2/frame/g"
	"github.com/gogf/gf/v2/net/ghttp"
	"github.com/gogf/gf/v2/util/gconv"
	"github.com/gogf/gf/v2/util/grand"
	"github.com/golang-jwt/jwt/v5"
	"github.com/yitter/idgenerator-go/idgen"
	"golang.org/x/crypto/bcrypt"
)

func D读取配置(上下文 context.Context, 键 string) *gvar.Var {
	配置值, 错误 := g.Cfg().Get(上下文, 键)
	if 错误 != nil {
		panic(错误)
	}
	return 配置值
}

type J接口结构体 struct {
	API实例 *apijson.APIJSON
}

// 注册相关
type (
	注册请求Req struct {
		g.Meta   `method:"POST" path:"/register"`
		Email    string `v:"required"`
		Password string `v:"required"`
		Code     string
	}

	注册响应Res struct {
		Msg string `json:"msg"`
	}

	注册页面请求Req struct {
		g.Meta `method:"GET" path:"/register"`
	}

	注册页面响应Res struct {
		CanReg bool   `json:"canReg"`
		Msg    string `json:"msg"`
	}
)

func (接口 *J接口结构体) Z注册(上下文 context.Context, 请求 *注册请求Req) (响应 *注册响应Res, 错误 error) {
	允许注册 := D读取配置(上下文, "app.canReg").Bool()
	if !允许注册 {
		return nil, gerror.New("注册已关闭")
	}

	// TODO: 防止暴力注册

	注册验证码 := D读取配置(上下文, "app.regCode").String()
	if 注册验证码 != 请求.Code {
		return nil, gerror.New("验证码不正确")
	}

	请求.Email = strings.TrimSpace(请求.Email)
	请求.Password = strings.TrimSpace(请求.Password)

	哈希密码, _ := bcrypt.GenerateFromPassword([]byte(请求.Password), bcrypt.DefaultCost)
	密码 := string(哈希密码)

	用户记录, 错误 := g.DB().Model("user").Where("username", 请求.Email).One()
	if 错误 != nil {
		return nil, 错误
	}

	if !用户记录.IsEmpty() {
		return nil, gerror.New("用户已存在")
	}

	用户ID := strconv.FormatInt(idgen.NextId(), 10)

	_, 错误 = g.DB().Model("user").Insert(g.Map{
		"user_id":  用户ID,
		"username": 请求.Email,
		"password": 密码,
	})

	if 错误 != nil {
		return nil, 错误
	}

	操作 := 接口.API实例.X新建动作(上下文, http.MethodPost, model.Map{
		"tag": "Groups",
		"Groups": model.Map{
			"groupId": 用户ID,
			"title":   "个人分组",
		},
		"GroupUser[]": []model.Map{
			{
				"groupId": 用户ID,
				"userId":  用户ID,
			},
		},
	})
	操作.B不验证访问权限 = true
	_, 错误 = 操作.J结果()

	return &注册响应Res{Msg: ""}, 错误
}

func (接口 *J接口结构体) Z注册页面(上下文 context.Context, 请求 *注册页面请求Req) (响应 *注册页面响应Res, 错误 error) {
	允许注册 := D读取配置(上下文, "app.canReg").Bool()
	消息 := ""
	用户记录, 错误 := g.DB().Model("user").One()
	if 错误 != nil {
		return nil, 错误
	}

	if 用户记录.IsEmpty() {
		消息 = "第一个用户注册后即为管理员"
		允许注册 = true
	}
	return &注册页面响应Res{CanReg: 允许注册, Msg: 消息}, nil
}

// 认证相关
type (
	认证请求Req struct {
		g.Meta `method:"POST" path:"/auth"`
		邮箱     string `v:"required"`
		密码     string `v:"required"`
	}
	认证响应Res struct {
		Token string `json:"token"`
	}
)

func (接口 *J接口结构体) R认证(上下文 context.Context, 请求 *认证请求Req) (响应 *认证响应Res, 错误 error) {
	用户记录, 错误 := g.DB().Model("user").One(g.Map{
		"username": strings.TrimSpace(请求.邮箱),
	})

	if 错误 != nil {
		return nil, 错误
	}

	密码 := strings.TrimSpace(请求.密码)

	if 用户记录 == nil || 用户记录.IsEmpty() || bcrypt.CompareHashAndPassword([]byte(gconv.String(用户记录.Map()["password"])), []byte(密码)) != nil {
		time.Sleep(time.Millisecond * time.Duration(grand.N(100, 500)))
		return nil, gerror.New("账户或密码错误")
	}

	令牌 := jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.MapClaims{
		"userId": 用户记录.Map()["user_id"],
		"iat":    time.Now().Unix(),
	})

	// 使用密钥签名并获取完整的编码令牌字符串
	令牌字符串, 错误 := 令牌.SignedString(jwt密钥)

	return &认证响应Res{Token: 令牌字符串}, 错误
}

// 文件上传相关
type (
	上传请求Req struct {
		g.Meta `method:"POST" path:"/upload"`
		File   *ghttp.UploadFile
	}

	上传响应Res struct {
		Path string `json:"path"`
	}

	图标请求Req struct {
		g.Meta `method:"GET" path:"/icon"`
		Name   string `json:"name"`
	}

	图标响应Res struct {
	}
)

func (接口 *J接口结构体) S上传(上下文 context.Context, 请求 *上传请求Req) (响应 *上传响应Res, 错误 error) {
	图标保存路径, _ := filepath.Abs(D读取配置(上下文, "app.iconSavePath").String())

	路径, 错误 := 请求.File.Save(图标保存路径, true)

	return &上传响应Res{Path: 路径}, 错误
}

func (接口 *J接口结构体) T图标(上下文 context.Context, 请求 *图标请求Req) (响应 *图标响应Res, 错误 error) {
	请求对象 := g.RequestFromCtx(上下文)
	图标保存路径, _ := filepath.Abs(D读取配置(上下文, "app.iconSavePath").String())

	路径 := filepath.Join(图标保存路径, 请求.Name)
	if !strings.HasPrefix(路径, 图标保存路径) {
		请求对象.Response.Status = 500
		请求对象.Response.Write("?")
		return
	}
	请求对象.Response.ServeFile(路径, false)
	return &图标响应Res{}, nil
}
